package ru.elifantiev.cityrouter.infrastructure.map;

import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
import android.util.Log;
import com.google.android.maps.GeoPoint;


public class LocationUtility {

    private static final String TAG = "LocationUtility";

    public static Location getLastLocation(LocationManager locationManager) {
        Location lastNet = null, lastGPS = null;
        try {
            lastNet = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
            lastGPS = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        } catch (IllegalArgumentException e) {
            // ignore (if provider(s) is unknown)
        }

        return getLastLocation(lastGPS,  lastNet);
    }

    public static Location getLastLocation(Context ctx) {
        return getLastLocation((LocationManager)ctx.getSystemService(Context.LOCATION_SERVICE));
    }

    public static GeoPoint pointFromLocation(Location location) {
        return new GeoPoint(
            (int) (location.getLatitude() * 1E6),
            (int) (location.getLongitude() * 1E6));
    }

    public static GeoPoint getDefaultPoint() {
        return new GeoPoint(55753511, 37609261);
    }

    private static Location getLastLocation(Location lastGPS, Location lastNet) {
        Location best = null;
        if (lastNet == null) {
            if (lastGPS != null)
                best = lastGPS;
        } else {
            if (lastGPS != null) {
                if (lastNet.getTime() > lastGPS.getTime())
                    best = lastNet;
                else
                    best = lastGPS;
            } else
                best = lastNet;
        }
        return best;
    }

    /**
     * Determines whether one Location reading is better than the current Location fix
     *
     * @param location            The new Location that you want to evaluate
     * @param currentBestLocation The current Location fix, to which you want to compare the new one
     * @param refreshInterval     The time span when location is fresh enough
     * @return boolean            The result of comparison
     */
    public static boolean isBetterLocation(Location location, Location currentBestLocation, long refreshInterval) {
        float distance[] = new float[1];
        float speed = 0.0f;
        long timeSpan = 0;
        if (currentBestLocation == null) {
            /**
             * Сперва проверим время старой локации.
             * Нам не нужна локация со старого рефреша!
             */
            return System.currentTimeMillis() - location.getTime() <= refreshInterval;
        }

        if (location.getAccuracy() > 3000) { // Must be acquired through preferences.
            Log.d(TAG, "Filtered out due to very loose accuracy");
            return false;
        }

        Location.distanceBetween(
                location.getLatitude(),
                location.getLongitude(),
                currentBestLocation.getLatitude(),
                currentBestLocation.getLongitude(),
                distance);
        timeSpan = location.getTime() - currentBestLocation.getTime();
        speed = (distance[0] / 1000f) / ((float)timeSpan / 3600000f);
        if(speed > 200) {
            Log.d(TAG, String.format("Filtered out due to high movement speed: %f km/h", speed));
            return false;
        }

        // Check whether the new location fix is newer or older
        boolean isSignificantlyNewer = timeSpan > refreshInterval;
        boolean isSignificantlyOlder = timeSpan < -refreshInterval;
        boolean isNewer = timeSpan > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            Log.d(TAG, "Significantly newer");
            return true;
            // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(),
                currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            Log.d(TAG, "More accurate");
            return true;
        } else if (isNewer && !isLessAccurate) {
            Log.d(TAG, "Newer and not less accurate");
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            Log.d(TAG, "Newer, from same provider and no significantly less accurate");
            return true;
        }
        return false;
    }

    /**
     * Checks whether two providers are the same
     */
    private static boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
            return provider2 == null;
        }
        return provider1.equals(provider2);
    }

}
